/*
 * Copyright (c) 2023-2025 elsfs Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.elsfs.cloud.common.mybatis.ext.sql;

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import org.apache.commons.compress.utils.Lists;
import org.elsfs.cloud.common.core.entity.BaseEntity;
import org.elsfs.cloud.common.mybatis.ext.sql.support.insert.StandardInsertIntoItem;
import org.elsfs.cloud.common.mybatis.ext.sql.support.insert.StandardValuesItem;
import org.elsfs.cloud.common.mybatis.ext.sql.support.update.CompositeSetItem;
import org.elsfs.cloud.common.mybatis.ext.sql.support.update.ExpressionSetItem;
import org.elsfs.cloud.common.mybatis.ext.sql.support.update.SetItem;
import org.elsfs.cloud.common.mybatis.ext.sql.support.update.StandardSetItem;
import org.elsfs.cloud.common.util.lang.CollectionUtils;
import org.elsfs.cloud.common.util.lang.NamingCase;
import org.elsfs.cloud.common.util.lang.StringUtils;

/**
 * 抽象插入SQL实现
 *
 * @param <C> 子类具体类型
 */
@SuppressWarnings("unchecked")
public abstract class AbstractInsertSql<C extends AbstractInsertSql<C>> extends CompositeSqlFragment
    implements InsertSql<C>, SqlParameterManagerAware {

  /** 具体子类实现引用 */
  protected final C childThis = (C) this;

  /** 插入表 */
  private String table;

  /** 插入字段 */
  private Collection<String> insertFields = Collections.emptyList();

  /** 冲突约束名称 */
  private String conflictConstraint;

  /** 插入值集合 */
  private final Collection<Map<String, Object>> values = Lists.newArrayList();

  /** 存在冲突处理 */
  private boolean hasConflictHandling;

  /** 冲突更新SQL */
  private ConflictUpdateSql<C> conflictUpdateSql;

  /** 忽略冲突 */
  private boolean ignoreConflict;

  /**
   * 插入的表
   *
   * @param entityClass 实体类
   * @return 具体实现
   */
  @Override
  public C insertInto(Class<? extends BaseEntity> entityClass) {
    this.table = TableInfoHelper.getTableInfo(entityClass).getTableName();

    return this.childThis;
  }

  /**
   * 插入的表
   *
   * @param table 表名
   * @return 具体实现
   */
  @Override
  public C insertInto(String table) {
    this.table = table;

    return this.childThis;
  }

  /**
   * 插入的字段
   *
   * @param fields 字段名称
   * @return 具体实现
   */
  @Override
  public C fields(String... fields) {
    this.insertFields = Arrays.asList(fields);

    return this.childThis;
  }

  /**
   * 插入的字段
   *
   * @param fields 字段名称枚举列表
   * @return 具体实现
   */
  @Override
  public C fields(Enum<?>... fields) {
    this.insertFields = Arrays.stream(fields).map(Enum::name).toList();

    return this.childThis;
  }

  /**
   * 插入的字段
   *
   * @param fields 字段名称
   * @return 具体实现
   */
  @Override
  public C fields(Collection<String> fields) {
    this.insertFields = new ArrayList<>(fields);

    return this.childThis;
  }

  /**
   * 插入的字段
   *
   * @param fields 字段名称枚举集合
   * @return 具体实现
   */
  @Override
  public C fieldsEnum(Collection<Enum<?>> fields) {
    this.insertFields = fields.stream().map(Enum::name).toList();

    return this.childThis;
  }

  /**
   * 插入的值
   *
   * @param value 值Map
   * @return 具体实现
   */
  @Override
  public C values(Map<String, Object> value) {
    if (value == null) {
      throw new MybatisPlusException("插入值Map不能为空");
    }
    this.values.add(value);

    return this.childThis;
  }

  /**
   * 批量插入的值集合
   *
   * @param values 值集合
   * @return 具体实现
   */
  @Override
  public C values(Collection<Map<String, Object>> values) {
    if (CollectionUtils.isEmpty(values)) {
      throw new MybatisPlusException("插入值Map集合不能为空");
    }
    this.values.addAll(values);

    return this.childThis;
  }

  /**
   * 插入冲突时
   *
   * @param constraintName 冲突的约束名称
   * @return 具体实现
   */
  @Override
  public C onConflict(String constraintName) {
    this.conflictConstraint = constraintName;
    this.hasConflictHandling = true;

    return this.childThis;
  }

  /**
   * 冲突时啥也不干
   *
   * @return 具体实现
   */
  @Override
  public C doNothing() {
    this.ignoreConflict = true;

    return this.childThis;
  }

  /**
   * 冲突时执行更新语句
   *
   * @return 冲突更新SQl
   */
  @Override
  public ConflictUpdateSql<C> doUpdate() {
    this.ignoreConflict = false;

    this.conflictUpdateSql = new ConflictUpdateSqlImpl();
    return this.conflictUpdateSql;
  }

  /** 构建SQL之前处理 */
  @Override
  protected void beforeBuild() {
    this.addSqlFragment(new StandardInsertIntoItem(this.table, this.insertFields));
    this.addSqlFragment(
        new StandardValuesItem(this.getSqlParameterManager(), this.insertFields, this.values));

    if (this.hasConflictHandling) {
      if (StringUtils.isBlank(this.conflictConstraint)) {
        throw new MybatisPlusException("UPSERT语句冲突的约束名称不能为空");
      }
      this.addSqlFragment(
          new TokenSqlFragment("ON CONFLICT ON CONSTRAINT " + this.conflictConstraint));

      if (this.ignoreConflict) {
        this.addSqlFragment(new TokenSqlFragment("DO NOTHING"));
      } else {
        this.addSqlFragment(new TokenSqlFragment("DO UPDATE"));
        this.addSqlFragment(this.conflictUpdateSql);
      }
    }
  }

  /** 冲突更新SQl实现 */
  public class ConflictUpdateSqlImpl implements ConflictUpdateSql<C> {

    /** 冲突值中的字段前缀 */
    private static final String EXCLUDE_FIELD_PREFIX = "excluded.";

    /** 更新设置项 */
    private final Collection<SetItem> setItems = Lists.newArrayList();

    /**
     * 返回上一级对象
     *
     * @return 上级对象
     */
    @Override
    public C end() {
      return AbstractInsertSql.this.childThis;
    }

    /**
     * 设置字段表达式
     *
     * @param field 字段名称
     * @param excludedField 冲突值中的字段名称
     * @return 具体实现
     */
    @Override
    public ConflictUpdateSql<C> setConflict(String field, String excludedField) {
      this.setItems.add(
          new ExpressionSetItem(
              field, EXCLUDE_FIELD_PREFIX + NamingCase.toUnderlineCase(excludedField), null));

      return this;
    }

    /**
     * 设置字段表达式
     *
     * @param field 字段枚举
     * @param excludedField 冲突值中的字段名称
     * @return 具体实现
     */
    @Override
    public ConflictUpdateSql<C> setConflict(Enum<?> field, Enum<?> excludedField) {
      this.setItems.add(
          new ExpressionSetItem(
              field.name(),
              EXCLUDE_FIELD_PREFIX + NamingCase.toUnderlineCase(excludedField.name()),
              null));

      return this;
    }

    /**
     * 设置字段表达式
     *
     * @param field 字段名称
     * @param expression 表达式
     * @param args 表达式参数
     * @return 具体实现
     */
    @Override
    public ConflictUpdateSql<C> set(String field, String expression, Object... args) {
      this.setItems.add(new ExpressionSetItem(field, expression, args));

      return this;
    }

    /**
     * 设置字段表达式
     *
     * @param field 字段枚举
     * @param expression 表达式
     * @param args 表达式参数
     * @return 具体实现
     */
    @Override
    public ConflictUpdateSql<C> set(Enum<?> field, String expression, Object... args) {
      this.setItems.add(new ExpressionSetItem(field.name(), expression, args));

      return this;
    }

    /**
     * 设置字段值
     *
     * @param field 字段名称
     * @param value 值
     * @return 具体实现
     */
    @Override
    public ConflictUpdateSql<C> setValue(String field, Object value) {
      this.setItems.add(
          new StandardSetItem(AbstractInsertSql.this.getSqlParameterManager(), field, value));

      return this;
    }

    /**
     * 设置字段值
     *
     * @param field 字段枚举
     * @param value 值
     * @return 具体实现
     */
    @Override
    public ConflictUpdateSql<C> setValue(Enum<?> field, Object value) {
      this.setItems.add(
          new StandardSetItem(
              AbstractInsertSql.this.getSqlParameterManager(), field.name(), value));

      return this;
    }

    /**
     * 批量设置字段值
     *
     * @param fieldValueMap 字段值Map
     * @return 具体实现
     */
    @Override
    public ConflictUpdateSql<C> setValues(Map<String, Object> fieldValueMap) {
      for (Map.Entry<String, Object> entry : fieldValueMap.entrySet()) {
        this.setValue(entry.getKey(), entry.getValue());
      }

      return this;
    }

    /**
     * 批量设置字段值
     *
     * @param fieldValueMap 字段枚举值Map
     * @return 具体实现
     */
    @Override
    public ConflictUpdateSql<C> setValuesEnum(Map<Enum<?>, Object> fieldValueMap) {
      for (Map.Entry<Enum<?>, Object> entry : fieldValueMap.entrySet()) {
        this.setValue(entry.getKey(), entry.getValue());
      }

      return this;
    }

    /**
     * 构建SQL片段
     *
     * @return SQL片段
     */
    @Override
    public String buildSqlFragment() {
      if (!AbstractInsertSql.this.ignoreConflict && this.setItems.isEmpty()) {
        throw new MybatisPlusException("冲突更新SQL必须设置更新项");
      }

      return new CompositeSetItem(this.setItems).buildSqlFragment();
    }
  }
}
